﻿package com.winmastergames.pathNodeSystem
{
	import flash.display.MovieClip;
	import flash.display.Stage;
	import flash.events.TimerEvent;
	import flash.geom.Point;
	import com.winmastergames.pathNodeSystem.pathNode;
	import com.anotherearlymorning.PixelPerfect.PixelPerfectCollisionDetection;
	import com.adventuresinactionscript.collisions.HitTest;
	import flash.geom.Rectangle;
	import flash.utils.Timer;
	/**
	 * ...
	 * @author     Advanced Flash Engine in ActionScript 3.
    Copyright (C) 2009  Dean Gardiner, Taylor Lodge

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
	 */
	public class pathHandler
	{
		public var nodeArray:Array = new Array();
		public var stageRef:Stage;
		public var trainArray:Array;
		public var drawLines:Boolean = false;
		public var linesArray:Array = new Array();
		public var wallReference:walls;
		public var calcCalled:Boolean = false;
		public var calcExecut:int = 0;
		public var finishPoint:Point;
		public var reUpdateTimer:Timer;
		public function pathHandler(stageReference:Stage) 
		{
			stageRef = stageReference;
			trace("[PathHandler]: Handler Created!");
		}
		
		public function reupdate(e:TimerEvent)
		{
			removeLines();
			updateLines();
			reUpdateTimer.stop();
		}
		
		public function calculatePath(start:Point, finish:Point, wallsRef:walls)
		{
			reUpdateTimer = new Timer(3500, 0);
			reUpdateTimer.addEventListener(TimerEvent.TIMER, reupdate);
			finishPoint = finish;
			wallReference = wallsRef;
			//Clear Nodes
			for (var i:int = 0; i < nodeArray.length; i++)
			{
				removeNodes();
			}
			for (var j:int = 0; j < linesArray.length; j++)
			{
				removeLines();
			}
			//=====================================================
			//Calculate Path and Creates Nodes and Lines.
			//=====================================================
			//Create Start Point
			createNode(start.x, start.y);
			
			//Calculate a Node
			var nc1:Point = autoCalculate(start);
			var nc2:Point = autoCalculate(nc1);
			var nc3:Point = autoCalculate(nc2);
			var nc4:Point = autoCalculate(nc3);
			var nc5:Point = autoCalculate(nc4);
			var nc6:Point = autoCalculate(nc5);
			var nc7:Point = autoCalculate(nc6);
			var nc8:Point = autoCalculate(nc7);
			var nc9:Point = autoCalculate(nc8);
			var nc10:Point = autoCalculate(nc9);
			var nc11:Point = autoCalculate(nc10);
			var nc12:Point = autoCalculate(nc11);
			var nc13:Point = autoCalculate(nc12);
			var nc14:Point = autoCalculate(nc13);
			var nc15:Point = autoCalculate(nc14);
			var nc16:Point = autoCalculate(nc15);
			var nc17:Point = autoCalculate(nc16);
			var nc18:Point = autoCalculate(nc17);
			var nc19:Point = autoCalculate(nc18);
			var nc20:Point = autoCalculate(nc19);
			
			//Create End Point
			createNode(finish.x, finish.y);
			//Update Lines
			removeLines();
			updateLines();
			
			//Merge Path Nodes that are within 40 pixels of another path Node.
			for (var l:Number = 0; l < nodeArray.length; l++)
			{
				try
				{
					var distance:Number = Point.distance(new Point(nodeArray[l].x, nodeArray[l].y), new Point(nodeArray[l + 1].x, nodeArray[l + 1].y))
					if (distance <= 25)
					{
						stageRef.removeChild(nodeArray[l]);
						nodeArray.splice(l, 1);
					}
				} catch(e:Error){}
			}
			//Update
			removeLines();
			updateLines();
			
			//ReRun Merging Code just incase we missed something.
			//Merge Path Nodes that are within 40 pixels of another path Node.
			for (var l:Number = 0; l < nodeArray.length; l++)
			{
				try
				{
					var distance:Number = Point.distance(new Point(nodeArray[l].x, nodeArray[l].y), new Point(nodeArray[l + 1].x, nodeArray[l + 1].y))
					if (distance <= 35)
					{
						stageRef.removeChild(nodeArray[l]);
						nodeArray.splice(l, 1);
					}
				} catch(e:Error){}
			}
			//Update
			removeLines();
			updateLines();
			
			//If any nodes touch each other remove them
			
			for (var m:int = 0; m < nodeArray.length; m++)
			{
				var obj1:pathNode = nodeArray[m];
				var obj2:pathNode = nodeArray[m + 1];
				if(obj2 != null){
					if (obj1.hitTestObject(obj2) == true)
					{
						stageRef.removeChild(nodeArray[m]);
						nodeArray.splice(m, 1);
					}
				}
			}
			
			//Update
			removeLines();
			updateLines();
			
			//Compare Distance of Node1 and Node2 to Node3 if Node2 is a longer distance away from Node3 compared to Node1
			// Then remove it (Runs 2 times)
			for (var na:int = 0; na < 2; na++)
			{
				for (var n:int = 0; n < nodeArray.length; n++)
				{
					try
					{
						//Setup Required Variables
						var nodeOne:pathNode = nodeArray[n - 1];
						var nodeOnePoint:Point = new Point(nodeOne.x, nodeOne.y);
						var nodeTwo:pathNode = nodeArray[n];
						var nodeTwoPoint:Point = new Point(nodeTwo.x, nodeTwo.y);
						var nodeThree:pathNode = nodeArray[n + 1];
						var nodeThreePoint:Point = new Point(nodeThree.x, nodeThree.y);
						//Get Distances
						var nodeOneDist:Number = Point.distance(nodeOnePoint, nodeThreePoint);
						var nodeTwoDist:Number = Point.distance(nodeTwoPoint, nodeThreePoint);
						//Remove Bad Node (if any)
						if (nodeTwoDist < nodeOneDist)
						{
							if ((n - 1) != 0)
							{
								stageRef.removeChild(nodeArray[n - 1]);
								nodeArray.splice(n - 1, 1);
							}
						}
					} catch(e:Error){}
				}
				
				//Update
				removeLines();
				updateLines();
			}
			
			//Check to see if any Lines are Hitting Walls.
			var problem:Boolean = false;
			for (var k:Number = 0; k < linesArray.length; k++)
			{
				if (wallsRef.hitTestObject(linesArray[k]) == true)
				{
					problem = true;
				}
			}
			if (problem == true)
			{
				calcCalled = true;
				calcExecut += 1;
				calculatePath(start, finish, wallsRef);
			} else {
				trace("Finished Path in " + calcExecut + " Recalculations.");
				calcCalled = false;
				calcExecut = 0;
			}
			//Update
			removeLines();
			updateLines();
			reUpdateTimer.start();
		}
		
		private function getAngle(X1:Number, Y1:Number, X2:Number, Y2:Number):Number
		{
			var dir = radtoDeg(Math.atan2((Y2 - Y1), (X2 - X1)));
			return dir;
		}
		
		private function radtoDeg(radians:Number):Number
		{
			var degrees = radians * 180/Math.PI;
			return degrees;
		}
		
		public function autoCalculate(point:Point):Point
		{
			var angle:Point = new Point((Math.random() * (1.0 - 0.1) + 0.1), (Math.random() * (1.0 - 0.1) + 0.1));
			var calcNode:pathNode = calculationNode(point.x, point.y);
			getCalc(calcNode, wallReference, angle);
			return new Point(calcNode.x, calcNode.y);
		}
		
		public function nodeCalculation(angle:Point, startPoint:Point):Point
		{
			var calcNode:pathNode = calculationNode(startPoint.x, startPoint.y);
			getCalc(calcNode, wallReference, angle);
			return new Point(calcNode.x, calcNode.y);
		}
		
		public function checkNode(node:pathNode, wall:walls):Boolean
		{
			var collide:Boolean = PixelPerfectCollisionDetection.isColliding(wall, node, stageRef, true, 255);
			if (collide == true)
			{
				trace(collide);
			}
			return wall.hitTestObject(node);
		}
		
		public function getCalc(calculationNode:pathNode, wallRef:walls, anglePoint:Point)
		{
			var BadPlacement:Boolean = false;
			while (BadPlacement == false)
			{
				BadPlacement = checkNode(calculationNode, wallRef);
				if (calculationNode.x >= stageRef.stageWidth ||
					calculationNode.x <= 0 ||
					calculationNode.y >= stageRef.stageHeight ||
					calculationNode.y <= 0)
					{
						BadPlacement = true;
					}
				calculationNode.x += anglePoint.x;
				calculationNode.y += anglePoint.y;
			}
			calculationNode.x -= 35;
			calculationNode.y -= 35;
			calculationNode.rotation = getAngle(calculationNode.x, calculationNode.y, finishPoint.x, finishPoint.y);
		}
		
		public function calculationNode(x:int, y:int):pathNode
		{
			var tempNode:pathNode = new pathNode(x, y, stageRef);
			nodeArray.push(tempNode);
			return tempNode;
		}
		
		public function createNode(x:int, y:int)
		{
			nodeArray.push(new pathNode(x, y, stageRef));
		}
		
		public function removeNodes()
		{
			for(var i:int = 0; i < nodeArray.length; i++)
			{
				stageRef.removeChild(nodeArray[i]);
			}
			nodeArray = new Array();
		}
		
		public function removeLines()
		{
			for(var i:int = 0; i < linesArray.length; i++)
			{
				stageRef.removeChild(linesArray[i]);
			}
			linesArray = new Array();
		}
		
		public function createLine(fromX:int, fromY:int, toX:int, toY:int)
		{
			var lineDrawing:MovieClip = new MovieClip();
 
			lineDrawing.graphics.lineStyle(1);
			lineDrawing.graphics.moveTo(fromX,fromY); ///This is where we start drawing
			lineDrawing.graphics.lineTo(toX, toY);
			stageRef.addChild(lineDrawing);
			linesArray.push(lineDrawing);
		}
		
		public function setTrains(trains:Array)
		{
			trainArray = trains;
		}
		
		function convert (radians:Number):Number
		{
			var degrees = radians*(180/Math.PI);
			return degrees;
		}
		
		public function updateLines()
		{
			if (drawLines == true)
			{
				for (var i:int = 0; i < nodeArray.length; i++)
				{
					try
					{
						nodeArray[i].rotation = getAngle(nodeArray[i].x, nodeArray[i].y, finishPoint.x, finishPoint.y);
					} catch (e:Error){}
					if (i < (nodeArray.length - 1))
					{
						createLine(nodeArray[i].x, nodeArray[i].y, nodeArray[i + 1].x, nodeArray[i + 1].y);
					}
				}
			}
		}
		
		public function updateTrains()
		{
			for (var i:int = 0; i < trainArray.length; i++)
			{
				// Movement.
				var xs:Number = 4.5 * Math.cos((trainArray[i].rotation * Math.PI) / 180);
				var ys:Number = 4.5 * Math.sin((trainArray[i].rotation * Math.PI) / 180);
				trainArray[i].x -= xs;
				trainArray[i].y -= ys;
				
				//Rotation
				var adj:Number=trainArray[i].y-nodeArray[trainArray[i].nextNode].y;
				var opp:Number=trainArray[i].x-nodeArray[trainArray[i].nextNode].x;
				var rot:Number=Math.atan2(adj,opp);
				trainArray[i].rotation=convert(rot);
				//HitTest with PathNodes
				for (var j:int = 0; j < nodeArray.length; j++)
				{
					if (trainArray[i].hitTestObject(nodeArray[j]) == true)
					{
						if(j == trainArray[i].nextNode){
							if ((j + 1) >= nodeArray.length)
							{
								trainArray[i].nextNode = 0;
							} else {
								trainArray[i].nextNode = j + 1;
							}
						}
					}
				}
			}
		}
	}

}